﻿using System;

using Microsoft.Xna.Framework;

using Ah.Testudin.Engine.Physics;
using Ah.Testudin.Engine.Events;
using Ah.Testudin.Engine.Events.Components;

namespace Ah.Testudin.Engine.Actors.Components
{
    public class PhysicsComponent : IActorComponent
    {
        #region Private Member Variables

        private const float ACCELERATION_SCALE = 1000.0f;

        private Matrix _rigidBodyTransform;

        private float _acceleration;

        private float _angularAcceleration;

        private readonly IPhysicsSimulator _physicsSimulator;

        #endregion

        #region Constructors

        public PhysicsComponent(
            Actor owner,
            Shape shape,
            Matrix rigidBodyTransform,
            Vector3 dimensions,
            float gravityAcceleration,
            IPhysicsSimulator physicsSimulator,
            IEventManager eventManager)
        {
            if (owner == null)
            {
                throw new ArgumentNullException("owner");
            }

            if (physicsSimulator == null)
            {
                throw new ArgumentNullException("physicsSimulator");
            }

            if (eventManager == null)
            {
                throw new ArgumentNullException("eventManager");
            }

            this.Owner = owner;

            _rigidBodyTransform = rigidBodyTransform;

            _physicsSimulator = physicsSimulator;

            switch (shape)
            {
                case Shape.Cube:

                    _physicsSimulator.AddCube(
                        dimensions,
                        gravityAcceleration,
                        owner);

                    break;

                case Shape.Capsule:

                    _physicsSimulator.AddCapsule(
                        dimensions,
                        gravityAcceleration,
                        owner);

                    break;

            }

            eventManager.TriggerEvent(
                new PhysicsComponentCreatedEvent(
                    this));
        }

        #endregion

        #region IActorComponent

        public Actor Owner { get; private set; }

        public void Update(GameTime gameTime)
        {
            TransformComponent transformComponent = this.Owner.GetComponent<TransformComponent>();

            if (transformComponent == null)
            {
                throw new Exception("Expected transform component");
            }

            Matrix actorTransform = transformComponent.Transform;

            if (_acceleration != 0)
            {
                float accelerationToApplyThisFrame =
                    _acceleration / ACCELERATION_SCALE * (float)gameTime.ElapsedGameTime.TotalMilliseconds;

                _physicsSimulator.ApplyForce(
                    actorTransform.Up,
                    accelerationToApplyThisFrame,
                    this.Owner.Id);
            }

            if (_angularAcceleration != 0)
            {
                float angularAccelerationToApplyThisFrame =
                    _angularAcceleration / ACCELERATION_SCALE * (float)gameTime.ElapsedGameTime.TotalMilliseconds;

                _physicsSimulator.ApplyTorque(
                    actorTransform.Up,
                    angularAccelerationToApplyThisFrame,
                    this.Owner.Id);
            }
        }

        public void Dispose()
        {
        }

        #endregion

        #region Public Methods

        public void ApplyForce(
            Vector3 direction,
            float newtons)
        {
            _physicsSimulator.ApplyForce(
                direction,
                newtons,
                this.Owner.Id);

        }

        public void ApplyTorque(
            Vector3 direction,
            float magnitude)
        {
            _physicsSimulator.ApplyTorque(
                direction,
                magnitude,
                this.Owner.Id);
        }

        public bool KinematicMove(Matrix newTransform)
        {
            return _physicsSimulator.KinematicMove(
                this.Owner.Id,
                newTransform);
        }

        public void ApplyAcceleration(float acceleration)
        {
            _acceleration = acceleration;
        }

        public void RemoveAcceleration()
        {
            _acceleration = 0;
        }

        public void ApplyAngularAcceleration(float angularAcceleration)
        {
            _angularAcceleration = angularAcceleration;
        }

        public void SetVelocity(Vector3 velocity)
        {
            _physicsSimulator.SetVelocity(
                this.Owner.Id,
                velocity);
        }

        public Vector3 GetVelocity()
        {
            return _physicsSimulator.GetVelocity(this.Owner.Id);
        }

        public void SetPosition(Vector3 position)
        {
            TransformComponent transformComponent = this.Owner.GetComponent<TransformComponent>();

            Matrix transform = transformComponent.Transform;

            transform.Translation = position;

            KinematicMove(transform);
        }

        #endregion
    }
}
